﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsxFactory.Modeling.VisualStudio;
using System.Diagnostics.Contracts;
using Microsoft.VisualStudio.TextTemplating;

namespace VsxFactory.Modeling.Strategies.VisualStudio
{
    [System.Diagnostics.DebuggerDisplay("Solution : Name={Name}")]
    internal class VSSolution : IVsSolution 
    {
        private SolutionNode solution;

        internal VSSolution(IVsSolutionExplorer explorer, HierarchyNode node)
        {
            System.Diagnostics.Contracts.Contract.Requires(explorer != null);
            Contract.Requires(node is SolutionNode);
            solution = node as SolutionNode;
            SolutionExplorer = explorer;
        }

        public string Name
        {
            get { return solution.Name; }
        }

        public IVsSolutionExplorer SolutionExplorer
        {
            get;
            private set;
        }

        public IEnumerable<IVsProjectItem> AllElements
        {
            get
            {
                return solution.AllElements.Select(e => Cast(e)).Where(e => e != null);
            }
        }

        internal IVsProjectItem Cast(HierarchyNode node)
        {
            if (node == null)
                return null;
            try
            {
                if (node.ExtObject is EnvDTE.Project && ((EnvDTE.Project)node.ExtObject).Kind == EnvDTE.Constants.vsProjectKindSolutionItems)
                    return new VSSolutionItems(this, node);

                if (node.ExtObject is EnvDTE.ProjectItem && ((EnvDTE.ProjectItem)node.ExtObject).Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder)
                {
                    return new VSFolder(this, node);
                }

                if (node.ExtObject is EnvDTE.ProjectItem && ((EnvDTE.ProjectItem)node.ExtObject).Kind == EnvDTE.Constants.vsProjectItemKindVirtualFolder)
                    return new VSSolutionFolder(this, node);

                if (node.IsProject)
                    return new VSProject(this, node.CastToProjectNode());

                if (node.IsSolution)
                    return this;

                //if (node.ExtObject is VSLangProj2
                //    return new VSReference(this, node);

                if ((node.ExtObject is EnvDTE.ProjectItem && ((EnvDTE.ProjectItem)node.ExtObject).Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
                    || (node.ExtObject is EnvDTE.ProjectItem && ((EnvDTE.ProjectItem)node.ExtObject).Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems))
                {
                    var projectNode = node.GetContainingProject();
                    if (projectNode == null) // Cas d'un fichier dans Solution Items
                        projectNode = ((HierarchyNode)SolutionItems.ExternalObject).CastToProjectNode();
                    return new VSProjectItem(new VSProject(this, projectNode), node);
                }
            }
            catch
            {
                // ignore unsupported project or item
            }
            return null;
        }

        public IVsSolution Solution
        {
            get { return this; }
        }

        public object ExternalObject
        {
            get { return solution; }
        }

        public IVsSolutionItems SolutionItems
        {
            get
            {
                return new VSSolutionItems(this);
            }
        }

        public T Find<T>(Func<T, bool> func) where T : class, IVsProjectItem
        {
            var p = SolutionItems.Find<T>(func);
            if (p != null)
                return p as T;

            foreach (var prj in AllElements.OfType<IVsProject>())
            {
                var p2 = prj.Find<T>(func);
                if (p2 != null)
                    return p2 as T;
            }

            return null;
        }

        public T FindByPath<T>(string fileName)  where T : class, IVsProjectItem
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(fileName));

            // Gestion du solution items
            var p = SolutionItems.FindByPath<IVsProjectItem>(fileName);
            if (p != null)
                return p as T;

            foreach(var prj in AllElements.OfType<IVsProject>())
            {
                var p2 = prj.FindByPath<IVsProjectItem>(fileName);
                if (p2 != null)
                    return p2 as T;
            }

            return null;
        }

        public IVsProjectFileItem AddItem(string name, byte[] content=null)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(name));

            var node = solution.AddItem(name, content);
            return SolutionItems.FindByPath<IVsProjectFileItem>(node.Path);
        }

        public IVsProject GetProject(Guid projectGuid)
        {
            var p = solution.GetProject(projectGuid);
            return p != null ? new VSProject(this, p) : null;
        }

        public string Path
        {
            get { return solution.ProjectDir; }
        }

        public IVsProject GetProject(string uniqueName)
        {
            var p = solution.GetProject(uniqueName);
            return p != null ? new VSProject(this, p) : null;
        }

        public IVsProject CreateProject(string projectTemplatesFolder, string solutionFolder, string name, string assemblyName, string template, string defaultNamespace)
        {
           var p = solution.CreateProject(SolutionExplorer, projectTemplatesFolder, solutionFolder, name, assemblyName, template, defaultNamespace, this.Name);
           return GetProject(p.ProjectGuid); 
        }

        /// <summary>
        /// Adds the template.
        /// </summary>
        /// <param name="solutionManagerService">The solution manager service.</param>
        /// <param name="name">Name of the resource.</param>
        /// <param name="binaryContent">if set to <c>true</c> [binary content].</param>
        /// <param name="templateFolder">The template folder.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public IVsProjectFileItem AddTemplateWithReplaceParameters(string name, bool binaryContent, string templateFolder, System.Resources.ResourceManager resourceManager, params string[] parameters)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(name));
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(templateFolder));
            System.Diagnostics.Contracts.Contract.Requires(resourceManager != null);

            // On regarde d'abord si le fichier existe dans le répertoire local
            string content = null;
            byte[] data = null;
            Encoding encoding = null;
            string fileName = System.IO.Path.Combine(templateFolder, name);
            if (System.IO.File.Exists(fileName))
            {
                if (binaryContent)
                {
                    data = System.IO.File.ReadAllBytes(fileName);
                }
                else
                {
                    content = System.IO.File.ReadAllText(fileName);
                    encoding = EncodingHelper.GetEncoding(fileName);
                    if (encoding == null)
                        encoding = Encoding.UTF8;
                }
            }
            else
            {
                // Sinon en tant que ressource du projet
                string tmp = System.IO.Path.GetFileNameWithoutExtension(name).Replace('.', '_');
                tmp = tmp.Replace('-', '_');
                object obj = resourceManager.GetObject(tmp);
                if (binaryContent)
                    data = (byte[])obj;
                else
                {
                    content = (string)obj;
                    encoding = Encoding.UTF8;
                }
            }

            if (content == null && !binaryContent || data == null && binaryContent)
            {
                throw new Exception(String.Format("Ressource {0} undefined. Unable to add the file '{1}' to the solution", name, fileName));    
            }

            if (!binaryContent)
            {
                if (parameters != null)
                {
                    for (int i = 0; i < parameters.Length; i += 2)
                    {
                        content = content.Replace(parameters[i], parameters[i + 1]);
                    }
                }
                data = encoding.GetBytes(content);
            }

            return AddItem(name, data);
        }

    }
}
